﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection.Emit;
using System.Collections;
using System.Reflection;
using System.Threading;

namespace Evaluant.Uss.Utility
{
    /// <summary>
    /// Generates IActivator classes implementations dynamically
    /// </summary>
    public class ActivatorFactory
    {
        private Dictionary<Type, IActivator> _ActivatorCache = new Dictionary<Type, IActivator>(20);

        public IActivator CreateActivator<T>()
        {
            return CreateActivator(typeof(T));
        }

        public IActivator CreateActivator(Type typeToActivate)
        {
            IActivator activator;

            if (_ActivatorCache.ContainsKey(typeToActivate))
            {
                return _ActivatorCache[typeToActivate];
            }

            lock (_ActivatorCache)
            {
                if (_ActivatorCache.ContainsKey(typeToActivate))
                {
                    return _ActivatorCache[typeToActivate];
                }

                // Create an assembly name
                AssemblyName assemblyName = new AssemblyName();
                assemblyName.Name = typeToActivate.FullName;

                // Create a new assembly with one module
                AssemblyBuilder newAssembly = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
                ModuleBuilder moduleBuilder = newAssembly.DefineDynamicModule(assemblyName.Name + ".dll");

                // Define a public class named "Activator" in the assembly.
                TypeBuilder typeBuilder = moduleBuilder.DefineType("Activator",
                    TypeAttributes.Public | TypeAttributes.Class,
                    typeof(object),
                    new Type[] { typeof(IActivator) });

                MethodBuilder methodBuilder =
                    typeBuilder.DefineMethod("IActivator.CreateInstance",
                    MethodAttributes.HideBySig |
                    MethodAttributes.NewSlot |
                    MethodAttributes.Public |
                    MethodAttributes.SpecialName |
                    MethodAttributes.Final |
                    MethodAttributes.Virtual,
                    typeof(object), new Type[0]);

                ILGenerator gen = methodBuilder.GetILGenerator();

                ConstructorInfo[] ctors = typeToActivate.GetConstructors();

                if (ctors.Length == 0)
                {
                    throw new ArgumentException("The type " + typeToActivate.FullName + " doesn't have a default constructor");
                }

                ConstructorInfo ctor = null;

                int index = 0;

                do
                {
                    ctor = ctors[index++];
                } while (ctor.GetParameters().Length != 0);

                gen.Emit(OpCodes.Newobj, ctor);
                gen.Emit(OpCodes.Ret);

                typeBuilder.DefineMethodOverride(
                    methodBuilder,
                    typeof(IActivator).GetMethod("CreateInstance"));

                // Load the type
                Type type = typeBuilder.CreateType();

                activator = Activator.CreateInstance(type) as IActivator;

                _ActivatorCache.Add(typeToActivate, activator);

            }

            // newAssembly.Save(assemblyName.Name + ".dll");

            return activator;
        }
    }
}
